#include "cic.h"
#include "mem_handler.h"

cic cpu;

inline void adi(u8 inst) {
	cpu.skip = cpu.a.check_overflow(inst & 0xf);
	cpu.a += inst;
}

inline void skai(u8 inst) {
	if (cpu.a == (inst & 0xf))
		cpu.skip = 1;
}

inline void lbli(u8 inst) {
	cpu.b.set_low(inst & 0xf);
}

inline void ldi(u8 inst) {
	cpu.a = inst & 0xf;
}

inline void l() {
	cpu.a = ram_read_nybble();
}

inline void x() {
	u4 c = cpu.a;
	cpu.a = ram_read_nybble();
	ram_write_nybble(c);
}

inline void nega() {
	// will be coma on 3195A
	u8 tmp = (0xf - cpu.a.value) + (1 - cpu.model);
	if (tmp > 0xf)
		cpu.skip = 1;

	cpu.a = tmp;
}

inline void out(u4 data) {
	cpu.port[cpu.b.get_low()] = data.value;
}

inline void sc() {
	// will be rc on 3195A
	cpu.c = 1 - cpu.model;
}

inline void rc() {
	// will be sc on 3195A
	cpu.c = cpu.model;
}

inline void s() {
	ram_write_nybble(cpu.a);
}

inline void rit() {
	pc_pop();
}

inline void coma() {
	// will be nega on 3195A
	u8 tmp = (0xf - cpu.a.value) + cpu.model;
	if (tmp > 0xf)
		cpu.skip = 1;

	cpu.a = tmp;
}

inline void in() {
	cpu.a.value = cpu.port[cpu.b.get_low()];
}

inline void xal() {
	u4 c = cpu.a;
	cpu.a = cpu.b.get_low();
	cpu.b.set_low(c.value);
}

inline void lxa() {
	cpu.x = cpu.a;
}

inline void xax() {
	u4 c = cpu.a;
	cpu.a = cpu.x;
	cpu.x = c;
}

inline void skm(u8 inst) {
	cpu.skip = ram_read_nybble().value & (1 << (inst & 3));
}

inline void ska(u8 inst) {
	cpu.skip = cpu.a.value & (1 << (inst & 3));
}

inline void rm(u8 inst) {
	ram_write_nybble(ram_read_nybble() & ~(1 << (inst & 3)));
}

inline void sm(u8 inst) {
	ram_write_nybble(ram_read_nybble() | (1 << (inst & 3)));
}

inline void lbmi(u8 inst) {
	cpu.b.set_high(inst);
}

inline void tl(u8 inst) {
	u8 off = rom_fetch_op();
	u8 seg = 2 * (inst & 3);
	if (off & 0x80)
		seg++;
	off &= 0x7f;

	cpu.pc.value = seg << 8 | off;
}

inline void tml(u8 inst) {
	u8 off = rom_fetch_op();
	u8 seg = 2 * (inst & 3);
	if (off & 0x80)
		seg++;
	off &= 0x7f;

	pc_push();

	cpu.pc.value = seg << 8 | off;
}

inline void t(u8 inst) {
	cpu.pc.i.offset = inst & 0x7f;
}

inline void cic_step()
{
#ifdef _DEBUG
	for (int i = 0; i < 4; i++)
		printf("%x", cpu.port[i]);
	printf(" ");
	for (int i = 0; i < 32; i++) {
		if (i == 16) printf(" ");
		printf("%x", cpu.ram[i].value);
	}
	printf(" ");
	printf("%x %x %02x %c ", cpu.x.value, cpu.a.value, cpu.b.value, cpu.skip ? 'S' : ' ');
	u16 this_pc = cpu.pc.value;
#endif

	u8 op = rom_fetch_op();

#ifdef _DEBUG
	printf("%03x: %02x\n", this_pc, op);
#endif

	if (cpu.skip)
	{
		cpu.skip = 0;
		return;
	}
	
	switch ((op & 0xf0) >> 4) {
	case 0: adi(op); break;
 	case 1: skai(op); break;
 	case 2: lbli(op); break;
 	case 3: ldi(op); break;

	case 4:
		switch (op & 0xf) {
		case 0: l(); break;
		case 1: x(); break;
		case 2: x(); cpu.skip = cpu.b.increment_low(); break;
		case 3: x(); cpu.skip = cpu.b.decrement_low(); break;
		case 4: nega(); break;
		case 6: out(cpu.a); break;
		case 7: out(0); break;
		case 8: sc(); break;
		case 9: rc(); break;
		case 0xa: s(); break;
		case 0xc: rit(); break;
		case 0xd: rit(); cpu.skip = 1; break;
		}
		break;

	case 5:
		switch (op & 0xf) {
		case 2: l(); cpu.skip = cpu.b.increment_low(); break;
		case 4: coma(); break;
		case 5: in(); break;
		case 7: xal(); break;
		case 0xc: lxa(); break;
		case 0xd: xax(); break;
		case 0xe: printf("SPECIAL MYSTERY INSTRUCTION\n"); break;
		}
		break;

	case 6:
		switch (op & 0xf) {
		case 0:
		case 1:
		case 2:
		case 3: skm(op); break;

		case 4:
		case 5:
		case 6:
		case 7: ska(op); break;

		case 8:
		case 9:
		case 0xa:
		case 0xb: rm(op); break;

		case 0xc:
		case 0xd:
		case 0xe:
		case 0xf: sm(op); break;
		}
		break;

	case 7:
		switch (op & 0xf) {
		case 3:	cpu.skip = cpu.a.check_overflow(cpu.c + ram_read_nybble().value);
		case 2: cpu.a += cpu.c;
		case 0: cpu.a += ram_read_nybble(); break;

		case 4:
		case 5:
		case 6:
		case 7: lbmi(op); break;

		case 8:
		case 9:
		case 0xa:
		case 0xb: tl(op); cpu.ticks++; break;

		case 0xc:
		case 0xd:
		case 0xe:
		case 0xf: tml(op); cpu.ticks++; break;
		}
		break;

	default:
		if (op & 0x80) {
			t(op);
			break;
		}
		else
			printf("Unimplemented op %02x\n", op);
		break;
	}
}

void cic_run(unsigned int ticks, const u8* input_stream)
{
	for (cpu.ticks = 0; cpu.ticks + 4 < ticks; cpu.ticks++)
	{
		int t3 = cpu.ticks + 4;
		int n = (t3 / 8) * 2;
		u8 din	= (input_stream[n]	>> (t3 % 8)) & 1;
		u8 dout	= (input_stream[n+1]>> (t3 % 8)) & 1;
		cpu.port[0] = (cpu.port[0] & 13) | (2 * din);
#ifdef _DEBUG
		if (dout != (cpu.port[0] & 1))
			printf("BADBADBAD: ");
		printf("%d-%d=%d ", din, dout, cpu.port[0] & 1);
		printf("%6i ", cpu.ticks);
#endif

		cic_step();
	}
}
